from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from pkg_resources import resource_string
import cometdemo.other.namegenerator as namegenerator
import db

import logging, os, os.path
log = logging.getLogger(os.path.basename(__file__))

import diff_match_patch
dmp = diff_match_patch.diff_match_patch()

import cometdemo.other.json as json
import urllib
import socket
import evserver.transports
import logging, os.path
from broker import send_amqp_message
import amqplib.client_0_8 as amqp


def document(request, document_id):
    key = urllib.quote(document_id)
    version, content = db.get(key)
    if content is None:
        version, content = db.set(key, lambda a: resource_string(__name__, 'textarea.txt').replace('\r',''))
        send_amqp_message('render', key)

    context = {
        'contentversion': str(version),
        'contentbox': content,
        'name': namegenerator.generate(),
        'key': key.replace('%',''),
    }

    return render_to_response('greed/index.html', context)


def output_view(request, document_id):
    key = urllib.quote(document_id)
    output = db.output_get(key)
    if not output:
        return HttpResponse('<html><body></body></html>')
    return HttpResponse(output)

def input_view(request, document_id, version=None):
    key = urllib.quote(document_id)
    if not version:
        version, _ = db.get(key)
        return HttpResponseRedirect('./%s/' % version)
    
    key = urllib.quote(document_id)
    _, content = db.get(key, version=int(version))
    return HttpResponse(content, mimetype="text/plain")



def ajax_push(request, document_id):
    key = urllib.quote(document_id)
    payload = request.raw_post_data
    if not isinstance(payload, unicode):
        payload = payload.encode('utf-8')

    patch_number, _, _, payload = json.read(payload)

    patches = dmp.patch_fromText(payload)

    payload=[None]
    conflicts=[None]
    def foo(content):
        if not isinstance(content, unicode):
            content = content.encode('utf-8')
        new_content, results = dmp.patch_apply(patches, content)

        conflicts[0] = sum(map(lambda a: 0 if True else 1, results))
        diffs = dmp.diff_main(content, new_content)
        dmp.diff_cleanupEfficiency(diffs);
        payload[0] = dmp.patch_toText(dmp.patch_make(diffs))
        return new_content

    version, new_content = db.set(key, foo)
    version = int(version)

    send_amqp_message(key, json.write([patch_number, version-1, version, payload[0]]))
    send_amqp_message('render', key)

    return HttpResponse(str(conflicts[0]), mimetype="text/plain")


def userlist_to_str(u):
    if not u:
        return ''
    r = ', '.join(u)
    if len(r) > 64:
        return r[:64] + '...'
    return r

def user_login(key, username):
    def foo(v):
        v.append( username )
        return v
    return user_change(key, username, foo)

def user_logout(key, username):
    def foo(v):
        if username in v:
            v.remove(username)
        return v
    return user_change(key, username, foo)

def user_change(key, username, foo):
    ukey = key + '.userlist'
    _, old_userlist = db.mc_get(ukey)
    if old_userlist is None:
        db.mc_set(ukey, lambda x:[])
    _, new_userlist = db.mc_set(ukey, foo)
    ul = userlist_to_str(new_userlist)
    if userlist_to_str(old_userlist) != ul:
        send_amqp_message(key, json.write(['userlist', '', '', ul]) )
        return True, ul
    return False, ul


def set_ridiculously_high_buffers(sd):
    for flag in [socket.SO_SNDBUF, socket.SO_RCVBUF]:
        for i in range(10):
            bef = sd.getsockopt(socket.SOL_SOCKET, flag)
            sd.setsockopt(socket.SOL_SOCKET, flag, bef*2)
            aft = sd.getsockopt(socket.SOL_SOCKET, flag)
            if aft <= bef or aft >= 16777216: # 16M
                break


def comet(request, document_id):
    t = evserver.transports.get_transport(request.GET.get('transport','basic'),
                                      callback=request.GET.get('callback','c0'),
                                      domain=request.GET.get('domain',''))
    key = urllib.quote(document_id)
    version = str(request.GET.get('v','1'))
    username = urllib.quote(str(request.GET.get('n','1')))
    # setup the amqp subscriber
    msgs = []
    def callback(msg):
        msgs.append(msg.body)
        msg.channel.basic_ack(msg.delivery_tag)

    conn = amqp.Connection('localhost', userid='guest', password='guest')

    ch = conn.channel()
    ch.access_request('/data', active=True, read=True)

    ch.exchange_declare(key, 'fanout', auto_delete=True)
    qname, _, _ = ch.queue_declare()
    ch.queue_bind(qname, key,)
    ch.basic_consume(qname, callback=callback)

    sd = conn.transport.sock
    sd.setblocking(False)
    set_ridiculously_high_buffers(sd)

    def iterator():
        try:
            yield t.start()
            modified, userlist = user_login(key, username)
            if not modified:
                yield t.write(json.write(['userlist', '', '', userlist]))

            old_version, old_content = db.get(key, version=version)
            new_version, content = db.get(key)
            if old_content is None or content is None:
                yield t.write('refresh')
                raise GeneratorExit

            if not isinstance(old_content, unicode):
                old_content = old_content.decode('utf-8')
            if not isinstance(content, unicode):
                content = content.decode('utf-8')
            payload = dmp.patch_toText(dmp.patch_make(old_content, content))
            if payload:
                yield t.write(json.write(['',old_version, new_version, payload]))

            output = db.output_get(key)
            yield t.write(json.write(['output', '', '', output]))

            while ch.callbacks:
                try:
                    while True: # until exception
                        ch.wait()
                except (TypeError,), e:
                    pass

                if not msgs:
                    yield t.write('ping')
                while msgs:
                    msg = msgs.pop(0)
                    if msg == 'newoutput':
                        new_output = db.output_get(key)
                        if not isinstance(new_output, unicode):
                            new_output = new_output.decode('utf-8')
                        if not isinstance(output, unicode):
                            output = output.decode('utf-8')
                        payload = dmp.patch_toText(dmp.patch_make(output, new_output))
                        output = new_output
                        if payload:
                            yield t.write(json.write(['output', 'update', 'update', payload]))
                    else: # just forward
                        yield t.write(msg)

                yield request.environ['x-wsgiorg.fdevent.readable'](conn.transport.sock, 60)
        except GeneratorExit:
            pass

        user_logout(key, username)

        try:
            ch.close()
        except Exception:
            pass
        try:
            conn.close()
        except Exception:
            pass

        ch.connection = None
        conn.channels = {}
        conn.connection = None
        conn.transport = None

    # build the response
    response = HttpResponse(iterator())
    for k, v in t.get_headers():
        response[k] = v
    return response
